/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System.Threading;

namespace NTasking.Internals
{

    /// <summary>
    /// The <see cref="RendezVous">RendezVous</see> class performs the actual execution
    /// of a rendez vous between a caller and an executor.
    /// </summary>
    internal class RendezVous
    {

        private ThreadWrapper _executorTask;


        private ExecutionContext _requeued;


        /// <summary>
        ///The call to be executed.
        /// </summary>
        internal readonly MethodCall Call;


        /// <summary>
        /// Gets or sets a flag indicating that this instance can be accepted immediately.
        /// </summary>
        internal bool Immediate { get; set; }


        /// <summary>
        /// Gets or sets the context in which the rendez-vous will be executed.
        /// </summary>
        internal ExecutionContext ExecutionContext { get; set; }


        /// <summary>
        /// Initializes a new instance of the <see cref="RendezVous">RendezVous</see> class.
        /// </summary>
        /// <param name="call">The call to be executed.</param>
        /// <param name="executionContext">The context in which the rendez-vous will be executed.</param>
        internal RendezVous(MethodCall call, ExecutionContext executionContext)
        {
            Call = call;
            ExecutionContext = executionContext;
        }


        /// <summary>
        /// Executes this rendez-vous.
        /// </summary>
        /// <returns>
        ///     <c>null</c> if the call is requeued on an entry which cant be accepted immediately;
        ///     otherwise, return this instance
        /// </returns>
        internal RendezVous Execute(ThreadWrapper abortResumeThread)
        {
            ThreadWrapper toBeResumed = abortResumeThread;

            _executorTask = ThreadWrapper.CurrentThread;

            while (true)
            {
                Prepare();

                _requeued = null;

                try
                {
                    if (toBeResumed != null)
                    {
                        toBeResumed = null;
                        abortResumeThread.AbortResume();
                    }

                    Tracer.Trace("Executing rendez-vous " + Call.Thread + " - " + Call);

                    // Execute the rendez-vous
                    Call.TargetInstance.Execute(Call, ExecutionContext);

                    // Mark the caller as finished
                    Call.TargetInstance.Owner.Finish(Call, true);

                    if (Call.Context.Exception == null)
                        Tracer.Trace("   Rendez-vous succeded " + Call.Thread + " - " + Call);
                    else
                        Tracer.Trace("   Rendez-vous " + Call.Thread + " - " + Call + " threw exception " + Call.Context.Exception.GetType());

                    return this;
                }
                catch (ThreadAbortException ex)
                {
                    if (ThreadWrapper.IsTaskInterrupt(ex, Call))
                        Tracer.Trace("   Rendez-vous " + Call.Thread + " - " + Call + " interrupted");
                    else if (ThreadWrapper.IsTaskRequeue(ex))
                    {
                        Tracer.Trace("   Rendez-vous " + Call.Thread + " - " + Call + " requeued");

                        // The execution has been requeued, get the entry to which the call has been requeued
                        _requeued = ThreadWrapper.RequeueTarget(ex);

                        // Prevent the exception from being re-thrown automatically
                        Thread.ResetAbort();
                    }
                    else
                    {
                        Tracer.Trace("   Rendez-vous " + Call.Thread + " - " + Call + " aborted");

                        // TheTask performing the execution of the rendez-vous has been aborted
                        Call.Context.Exception = Errors.AcceptAborted(Call);
                        Call.TargetInstance.Owner.Finish(Call, true);
                    }
                }
                finally
                {
                    Cleanup();
                }

                // At this point the call has been requeued

                if (_requeued == null)
                    // If the rendez-vous between the original call and the requeued execution can't be executed
                    // immediately, return null to indicate that the rendez-vous has been requeued
                    return null;

                // The rendez-vous between the original call and the requeued execution can be executed
                // immediately, so replace executor by the requeue target and execute the rendez-vous
                ExecutionContext = _requeued;
            }
        }


        /// <summary>
        /// Prepare the calling and executing tasks for execution of the rendez-vous.
        /// </summary>
        private void Prepare()
        {
            ThreadWrapper callerTask = Call.Thread;
            SynchronizedMethod target = Call.TargetInstance;
            SynchronizedObject owner = target.Owner;

            // Prevent the executor task from beeing aborted
            using (_executorTask.NoAbort())
            {

                // Inherit priority as required
                _executorTask.StartPriorityInherit(owner.IsThreaded ? callerTask.CurrentPriority : owner.Priority);

                // Mark the synchronized method as currently  being accepted in order to be able to
                // detect nested accepts
                target.CurrentlyBeingAccepted = true;

                // Push the current execution on the current task's call stack
                _executorTask.PushCall(target);

                // Push the current execution on the executor's call stack
                owner.PushCall(Call);
            }
        }


        /// <summary>
        /// Cleanup after the rendez-vous is finished
        /// </summary>
        private void Cleanup()
        {
            SynchronizedMethod target = ExecutionContext.Target;
            SynchronizedObject owner = target.Owner;

            // Prevent the executor task from beeing aborted
            using (_executorTask.NoAbort())
            {
                if (Immediate && _requeued == null)
                {
                    // If the rendez-vous was for immediate accept and has not been
                    // requeued on an entry which can be accepted immediately, then
                    // wake-up the caller
                    Call.CallLock.Pulse();
                }

                // Pop the current execution from the acceptors's call stack
                owner.PopCall();

                // Pop the current execution from the current task's call stack
                _executorTask.PopCall();

                // Mark that the synchronized method as not beeing accepted
                target.CurrentlyBeingAccepted = false;

                // Un-inherit priority
                _executorTask.EndPriorityInherit();
            }
        }

    }

}
